﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using MentalAlchemy.Atomics;
using MentalAlchemy.Molecules.MachineLearning;

namespace MentalAlchemy.Molecules.NevaAlgorithm.nevamod
{
	public class Neva2 : Neva
	{
		private float[,] transMatrix;

		public const string DEFAULT_TRANSMATRIX_FILE = "transm.cfg";

		#region - Properties. -
		/// <summary>
		/// Matrix of successull transitions.
		/// </summary>
		public float[,] TransMatrix
		{
			get { return transMatrix; }
			set { transMatrix = value; }
		}

		/// <summary>
		/// Indicates whether logging is turned on.
		/// </summary>
		public bool EnableLogging
		{
			get { return LogWriter.Instance().Enabled; }
			set { LogWriter.Instance().Enabled = value; }
		}
		#endregion

		#region - Construction. -
		public Neva2()
		{
			contents = new Neva2Contents();

			// load successfull transitions matrix.
			LoadTransMatrix(DEFAULT_TRANSMATRIX_FILE);
		}
		#endregion

		#region - Initialization. -
		public void LoadTransMatrix (string filename)
		{
			if (!File.Exists(filename)) return;

			var lines = FileIO.ReadAllLines(filename);
			var tempM = new List<float[]>();
			for (int i = 0; i < Operators.MUTATION_TYPE_COUNT; i++)
			{
				var tempStr = lines[i].Split('\t');
				tempStr = Strings.RemoveEmptyElements(tempStr);
				tempM.Add(VectorMath.CreateFromStringsArray(tempStr));
			}
			transMatrix = MatrixMath.CreateFromRowsList(tempM);
		}
		#endregion

		#region - Public methods. -
		public override void Run(NevaParameters parameters)
		{
			//
			// validate [parameters].
			if (!EAElements.ValidateParameters(parameters)) throw new Exception("[Neva.Run]: Invalid parameters setting or fitness function is undefined.");

			LogWriter.Instance().WriteLine("Init");
			Init(parameters);
			LogWriter.Instance().WriteLine("Evaluate");
			Evaluate(parameters);

			for (contents.GenerationNumber = 0; contents.GenerationNumber < parameters.GenerationsNumber; ++contents.GenerationNumber)
			{
				LogWriter.Instance().WriteLine("> Generation " + contents.GenerationNumber);
				LogWriter.Instance().WriteLine("Selection");
				contents.SelPopul = EAElements.TournamentSelection(contents.Popul, parameters.TournamentSize, parameters.RNG);
				LogWriter.Instance().WriteLine("Cross");
				contents.Popul = Neva2Elements.Cross(contents.SelPopul, parameters);
				//contents.Popul = contents.SelPopul;
				if (TransMatrix != null)
				{
					LogWriter.Instance().WriteLine("Mutate (transitions matrix)");
					contents.Popul = Neva2Elements.Mutate(contents.Popul, parameters, TransMatrix);
				}
				else
				{
					LogWriter.Instance().WriteLine("Mutate");
					contents.Popul = Neva2Elements.Mutate(contents.Popul, parameters);
				}

				//
				// Define the best individual.
				var tempBest = contents.Popul[0];
				for (int i = 1; i < contents.Popul.Count; i++)
				{
					var ind = contents.Popul[i];
					if (FitnessComparator.IsBetter(ind.Fitness, tempBest.Fitness))
					{
						tempBest = ind;
					}
				}
				if (contents.BestIndividual == null || FitnessComparator.IsBetter(tempBest.Fitness, contents.BestIndividual.Fitness))
				{
					LogWriter.Instance().WriteLine("Update the best individual");

					Fitness fitTrain;
					Fitness fit;
					//if (contents.BestIndividual != null)
					//{
					//    fitTrain = parameters.FitnessFunction.Calculate(((Neva2Ind)contents.BestIndividual).Network);
					//    fit = parameters.FitnessFunction.Test(((Neva2Ind)contents.BestIndividual).Network);
					//    LogWriter.Instance().Write("Before:\t(" + contents.BestIndividual.Fitness + "\t:\t" + fitTrain + "\t:\t" + fit + ")\n");

					//    ((Neva2Ind)contents.BestIndividual).BuildNetwork();
					//    fitTrain = parameters.FitnessFunction.Calculate(((Neva2Ind)contents.BestIndividual).Network);
					//    fit = parameters.FitnessFunction.Test(((Neva2Ind)contents.BestIndividual).Network);
					//    LogWriter.Instance().Write("Before + rebuild ANN:\t(" + contents.BestIndividual.Fitness + "\t:\t" + fitTrain + "\t:\t" + fit + ")\n");
					//}

					contents.BestIndividual = tempBest.Clone();
					
					//fitTrain = parameters.FitnessFunction.Calculate(((Neva2Ind)contents.BestIndividual).Network);
					//fit = parameters.FitnessFunction.Test(((Neva2Ind)contents.BestIndividual).Network);
					//LogWriter.Instance().Write("After:\t(" + contents.BestIndividual.Fitness + "\t:\t" + fitTrain + "\t:\t" + fit + ")\n");

					//((Neva2Ind)contents.BestIndividual).BuildNetwork();
					//fitTrain = parameters.FitnessFunction.Calculate(((Neva2Ind)contents.BestIndividual).Network);
					//fit = parameters.FitnessFunction.Test(((Neva2Ind)contents.BestIndividual).Network);
					//LogWriter.Instance().Write("After + rebuild aNN:\t(" + contents.BestIndividual.Fitness + "\t:\t" + fitTrain + "\t:\t" + fit + ")\n");
				}

				//
				// implement elitism via substitution of the 1st child with the best individual found.
				if (parameters.UseElitism)
				{
					contents.Popul[0] = contents.BestIndividual.Clone();
				}
				LogWriter.Instance().WriteLine("Collecting stats");
				CollectStats();
			}
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Checks activations for consistency.
		/// Throws an exception if something is wrong.
		/// </summary>
		/// <returns></returns>
		public void TestActivations()
		{
			for (int i = 0; i < contents.Popul.Count; i++)
			{
				var ind = contents.Popul[i];
				if (!((Neva2Ind) ind).TestActivations())
				{
					throw new Exception("Activations inconsistent for individual " + i);
				}
			}
		}

		#endregion
	}
}
